package proto;

import java.io.BufferedReader;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Main {

	private static HashMap<String, BuildingBlock> blocks = new HashMap<String, BuildingBlock>();
	private static HashMap<String, Wire> wires = new HashMap<String, Wire>();
	private static HashMap<String, Object> ports = new HashMap<String, Object>();
	
	private static String compsitePath = "";
	
	public static boolean verbose = true;
	
	public static void main(String[] args) {
		BreadBoard bb = new BreadBoard();
		java.io.InputStreamReader reader = new java.io.InputStreamReader(System.in);
		java.io.BufferedReader buffer = new BufferedReader(reader);

		System.out.println("Szoftver labor 4\n6. Szkeleton by Team Parizer");
		
		try{		
			while(true){
			String line = buffer.readLine();
				executeCommand(bb, line);		
			}
		} catch (java.io.IOException e){
			System.out.println(e.getMessage());			 
		}

	}

	/**
	 * Man, this shit is absolutely, f*cking ugly!
	 * @param bb
	 * @param command
	 * @return
	 */
	public static Boolean executeCommand(BreadBoard bb, String command){
		String cmd[] = command.split(" ");
		if (cmd[0].equalsIgnoreCase("addBlock")){
			if(cmd.length < 3){
				System.out.println("Nem megfelelő paraméterezés!");
				return false;
			}
			BuildingBlock block = null;
			String blockName = cmd[1];
			String split[] = cmd[2].split("\\(");
			String blockType = split[0].toUpperCase();
			Integer inputs = (split.length > 1 ? Integer.parseInt((split[1].substring(0, split[1].length()-1))) : 0);
			
			if(blockType.equals("AND")){
				if(inputs == 0){
					block = new AndGate(blockName);
					//System.out.println("added block name: " + blockName + " type: " + blockType);
				} else {
					block = new AndGate(blockName, inputs);
					//System.out.println("added block name: " + blockName + " type: " + blockType + " inputs: " + inputs);
				}			
			} else if(blockType.equals("OR")){
				if(inputs == 0){
					block = new OrGate(blockName);	
					//System.out.println("added block name: " + blockName + " type: " + blockType);
				} else {
					block = new OrGate(blockName, inputs);
					//System.out.println("added block name: " + blockName + " type: " + blockType + " inputs: " + inputs);
				}
			} else if(blockType.equals("NOT")){
				if(inputs == 0){
					block = new NotGate(blockName);	
					//System.out.println("added block name: " + blockName + " type: " + blockType);
				} else {		
					System.out.println("invalid command: " + command);
				}
			} else if(blockType.equals("SWITCH")){
				if(inputs == 0){
					block = new Switch(blockName);	
					//System.out.println("added block name: " + blockName + " type: " + blockType);
				} else {		
					System.out.println("invalid command: " + command);
				}
			} else if(blockType.equals("SEQGEN")){
				if(inputs == 0){
					block = new SequenceGen(blockName);	
					//System.out.println("added block name: " + blockName + " type: " + blockType);
				} else {		
					System.out.println("invalid command: " + command);
				}
			} else if(blockType.equals("LED")){
				if(inputs == 0){
					block = new LED(blockName);	
					//System.out.println("added block name: " + blockName + " type: " + blockType);
				} else {		
					System.out.println("invalid command: " + command);
				}
			} else if(blockType.equals("SCOPE")){
				if(inputs == 0){
					block = new Oscilloscope(blockName);	
					//System.out.println("added block name: " + blockName + " type: " + blockType);
				} else {		
					System.out.println("invalid command: " + command);
				}
			} else if(blockType.equals("COMPOSITE")){
				// TODO
				if(inputs == 0){
					block = new Composite(Main.compsitePath + blockName + ".comp");	
					//System.out.println("added block name: " + blockName + " type: " + blockType);
				} else {		
					System.out.println("invalid command: " + command);
				}
			}
			
			if(block != null){
				bb.addBlock(block);
				blocks.put(blockName, block);
			} else {
				System.out.println("invalid command: " + command);
			}
		} else if (cmd[0].equalsIgnoreCase("addWire")){
			//Bemenet: addWire <név> <outBlock>.out[[láb]] <inBlock>.in[[láb]]
			//Kimenet: added wire name: <név> from: <outBlock>.out[[láb]] to: <inBlock>.in[[láb]]
			if(cmd.length < 4){
				System.out.println("Nem megfelelő paraméterezés!");
				return false;
			}
			String wireName = cmd[1];
			String splitOut[] = cmd[2].split("\\.");
			String splitIn[] = cmd[3].split("\\.");
			String splitOutNum[] = splitOut[1].split("\\[");
			String splitInNum[] = splitIn[1].split("\\[");
			Integer outNum = (splitOutNum.length > 1 ? Integer.parseInt((splitOutNum[1].substring(0, splitOutNum[1].length()-1))) : null);
			Integer inNum = (splitInNum.length > 1 ? Integer.parseInt((splitInNum[1].substring(0, splitInNum[1].length()-1))) : null);
				
			Wire wire = new Wire(
					wireName,
					(BuildingBlock)blocks.get(splitIn[0]), 
					getInRef(blocks.get(splitIn[0]), inNum), 
					(BuildingBlock)blocks.get(splitOut[0]), 
					getOutRef(blocks.get(splitOut[0]), outNum));
			
			bb.addWire(wire);
			wires.put(wireName, wire);
			ports.put(splitIn[0] + ".in" + ((inNum == null) ? "" : "[" + inNum.toString() + "]"), getInRef(blocks.get(splitIn[0]), inNum));
			ports.put(splitOut[0] + ".out" + ((outNum == null) ? "" : "[" + outNum.toString() + "]"), getOutRef(blocks.get(splitOut[0]), outNum));
			
			//System.out.println("added wire " + wireName + 
			//		" from: " + splitOut[0].toString() + ".out" + ((outNum == null) ? "" : "[" + outNum.toString() + "]") +
			//		" to: " + splitIn[0].toString() + ".in" + ((inNum == null) ? "" : "[" + inNum.toString() + "]"));
		} else if (cmd[0].equalsIgnoreCase("exit")){
			System.out.println("exiting");			
			System.exit(0);
			
			return true;
				
		} else if (cmd[0].equalsIgnoreCase("getBlockState")){
			String blockName = cmd[1];
			BuildingBlock block = (BuildingBlock)blocks.get(blockName);
			if (!(block instanceof Composite)){
				System.out.println("block " + blockName + ".out is: " + getOutRef(block, null).toString());
			} else {
				Composite comp = (Composite)block;
				for(int i = 0; i < comp.getOutputs().size(); i++){
					System.out.println("block " + blockName + ".out[" + i + "] is: " + comp.getOutputs().get(i).getState());					
				}
			}
			
		} else if (cmd[0].equalsIgnoreCase("getMemory")){
			if(cmd.length < 2){
				System.out.println("Nem megfelelő paraméterezés!");
				return false;
			}
			Oscilloscope block = ((Oscilloscope)blocks.get(cmd[1]));
			List<Boolean> mem = block.getMemory();
			String seqString = "oscilloscope " + block.getName() + " memory: ";
			for(Boolean b : mem){
				seqString += ((b == true) ? "1" : "0");
			}
			System.out.println(seqString);
			
		} else if (cmd[0].equalsIgnoreCase("getSeq")){
			if(cmd.length < 2){
				System.out.println("Nem megfelelő paraméterezés!");
				return false;
			}
			SequenceGen block = ((SequenceGen)blocks.get(cmd[1]));
			List<Boolean> seq = block.getSeq();
			String seqString = "sequenceGen " + block.getName() + " has: ";
			for(Boolean b : seq){
				seqString += ((b == true) ? "1" : "0");
			}
			System.out.println(seqString);
		} else if (cmd[0].equalsIgnoreCase("getWire")){
			if(cmd.length < 2){
				System.out.println("Nem megfelelő paraméterezés!");
				return false;
			}
			Wire wire = (Wire) wires.get(cmd[1]);
			System.out.println("wire " + cmd[1] + " from: " + getKeyFromValue(ports, wire.getOutput()) + " to: " + getKeyFromValue(ports, wire.getInput()));
		} else if (cmd[0].equalsIgnoreCase("help")){
						
			String helpUzenet =

			"addBlock <név> <típus>[(<bemenet_száma>)]" + " - Építőelem felvétele \n" +
			"addWire <név> <outBlock>.out[[láb]] <inBlock>.in[[láb]]" + " - Vezeték felvétele \n" +
			"exit" + " - Kilépés \n" +
			"getBlockState <név>" + " - Építőelem állapota \n" +
			"getMemory <név>" + " - Oszcilloszkóp memóriája \n" +
			"getSeq <név>" + " - Szekvencia generátor megadott szekvenciája \n" +
			"getWire <nev>" + " - Vezeték lekérése \n" +
			"help" + " - Lehetséges parancsok listása \n" +
			"listBlocks" + " - Építőelemek felsorolása \n" +
			"listWires" + " - Vezetékek felsorolása \n" +
			"loadComposite <név>" + " - Kompozitelem betöltése \n" +
			"modifyWire <név> <outBlock>[[láb]] <inBlock>[[láb]]" + " - Vezeték átkötése \n" +
			"removeBlock <név>" + " - Építőelem eltávolítása \n" +
			"removeWire <név>" + " - Vezeték eltávolítása \n" +
			"saveComposite <név>" + " - Kompozitelem felvétele \n" +
			"setSeq <név> <szekvencia>" + " - Építőelem eltárolása \n" +
			"startSimulation [-novalidate] [-nostability] [depth(<int>)]" + " - Szimuláció indítása \n" +
			"step [<lépés_száma>]" + " -  \n" +
			"stopSimulation" + " - Szimuláció megállítása \n" +
			"swichOn <név>" + " - Kapcsoló igaz állapotba állítása \n" +
			"swichOff <név>" + " - Kapcsoló hamis állapotba állítása \n";
			
			
			System.out.println("Lehetséges parancsok:\n" + helpUzenet);
			
			
			
		} else if (cmd[0].equalsIgnoreCase("listBlocks")){
			//Bemenet: listBlocks
			//Kimenet: listing blocks: name: <név> type: <típus> [in: <bemenetek_száma>] [out: <kimenetek_száma>]
			Set set = blocks.keySet();
			Iterator i = set.iterator();
			
			System.out.println("listing blocks:");
			
			while(i.hasNext()){
				System.out.println(getBlockInfo((String)i.next()));
			}
		} else if (cmd[0].equalsIgnoreCase("listWires")){
			//Bemenet: listWires
			//Kimenet: listing wires: <vezeték1>, <vezeték2>
			Set set = wires.keySet();
			Iterator i = set.iterator();
			String wireList = "";
			
			while(i.hasNext()){
				wireList += (String)i.next() + ", ";
			}
			System.out.println("listing wires: " + wireList.substring(0, ((wireList.length() < 2) ? 0 : wireList.length()-2)));
			
		} else if (cmd[0].equalsIgnoreCase("loadComposite")){

			if(cmd.length > 0){
				//loadCompozit letöröl mindent itt, is megcsinálni ezt:
				
				Main.blocks = new HashMap<String, BuildingBlock>();
				Main.wires = new HashMap<String, Wire>();
				Main.ports = new HashMap<String, Object>();
				
								
				bb.loadComposite(Main.compsitePath + cmd[1] + ".comp");
				
				
				System.out.println("composite " + cmd[1] + " loaded");			
			}			
			
			// TODO Hiba ág

		} else if (cmd[0].equalsIgnoreCase("modifyWire")){
			//TODO
		} else if (cmd[0].equalsIgnoreCase("removeBlock")){
			//Bemenet: removeBlock <név>
			//Kimenet: removing block: <név>
			//Kimenet: removed block: <név>
			BuildingBlock block = blocks.get(cmd[1]);
			
        	Wire copyWires[] = new Wire[bb.getWires().size()];
        	bb.getWires().toArray(copyWires);  	
        	
        	for (Wire w: copyWires){
        		if (w.getInBlock().equals(block) || w.getOutBlock().equals(block)){        			
        			wires.remove(w.getName());
        			
        		}
	        } 
        	
			bb.deleteBlock(block);	
			blocks.remove(cmd[1]);
			
		} else if (cmd[0].equalsIgnoreCase("removeWire")){
			bb.removeWire(wires.get(cmd[1]));
			wires.remove(cmd[1]);


		} else if (cmd[0].equalsIgnoreCase("saveComposite")){

			if(cmd.length > 0){
				bb.saveComposite(Main.compsitePath + cmd[1] + ".comp");
				
				//saveCompozit letöröl mindent itt, is megcsinálni ezt:
				Main.blocks = new HashMap<String, BuildingBlock>();
				Main.wires = new HashMap<String, Wire>();
				Main.ports = new HashMap<String, Object>();
				
				System.out.println("composite " + cmd[1] + " saved");
			}			
			
			// TODO Hiba ág
			
			
		} else if (cmd[0].equalsIgnoreCase("setSeq")){
			List<Boolean> seq = new ArrayList<Boolean>();
			if(cmd.length > 2){
				CharacterIterator it = new StringCharacterIterator(cmd[2]);
				for(Character c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
				   if(c.equals(new Character('1'))){
					   seq.add(true);		
				   } else if (c.equals(new Character('0'))){
					   seq.add(false);
				   }
				}
				((SequenceGen)blocks.get(cmd[1])).setSeq(seq);
			}
		} else if (cmd[0].equalsIgnoreCase("startSimulation")){
			bb.startSimulation(0);
		} else if (cmd[0].equalsIgnoreCase("step")){
			Integer step;
			if(cmd.length == 1){
				step = 1;
			} else {
				step = Integer.parseInt(cmd[1]);
			}
			bb.startSimulation(step);
		} else if (cmd[0].equalsIgnoreCase("stopSimulation")){
			bb.endSimulation();
		} else if (cmd[0].equalsIgnoreCase("switchOn")){
			Boolean state = true;
			((Switch)blocks.get(cmd[1])).setState(state);
		} else if (cmd[0].equalsIgnoreCase("switchOff")){
			Boolean state = false;
			((Switch)blocks.get(cmd[1])).setState(state);
		} else {
			System.out.println("unkown command: " + command);
		}
		return true;
	}

	
	
	private static Input getInRef(Object obj, Integer num){
		if (num != null){
			num = num-1;
		}
		if (num != null && num < 0 ){
			return null; // Ne szálljon el ha valaki nullától indexélné a lábakat. :) 
		}
		
		if(obj instanceof AndGate){
			if(num < ((AndGate)obj).getInputs().size()){
				return((AndGate)obj).getInputs().get(num);
			}else{
				return null;
			}
		} else if (obj instanceof OrGate){
			if(num < ((OrGate)obj).getInputs().size()){
				return((OrGate)obj).getInputs().get(num);
			} else{
				return null;
			}
		} else if (obj instanceof NotGate){
			return((NotGate)obj).getInputs().get(0);
		} else if (obj instanceof LED){
			return ((LED)obj).getInput();
		} else if (obj instanceof Switch){
			return null;
		} else if (obj instanceof SequenceGen){
			return null;
		} else if (obj instanceof Oscilloscope){
			return ((Oscilloscope)obj).getInput();
		} else if (obj instanceof Composite){
			if(num < ((Composite)obj).getInputs().size()){
				return ((Composite)obj).getInputs().get(num);
			} else {
				return null;
			}
			
		} 
		return null;
	}
	
	private static Output getOutRef(Object obj, Integer num){
		if (num != null){
			num = num-1;
		}
		if (num != null && num < 0 ){
			return null; // Ne szálljon el ha valaki nullától indexélné a lábakat. :) 
		}
		
		if(obj instanceof AndGate){
			return((AndGate)obj).getOutput();
		} else if (obj instanceof OrGate){
			return((OrGate)obj).getOutput();
		} else if (obj instanceof NotGate){
			return((NotGate)obj).getOutput();
		} else if (obj instanceof LED){
			return null;
		} else if (obj instanceof Switch){
			return ((Switch)obj).getOutput();
		} else if (obj instanceof SequenceGen){
			return ((SequenceGen)obj).getOutput();
		} else if (obj instanceof Oscilloscope){
			return null;
		} else if (obj instanceof Composite){
			if( num < ((Composite)obj).getOutputs().size()){
				return ((Composite)obj).getOutputs().get(num);
			} else {
				return null;
			}
		} 
		return null;
	}
	
	private static String getKeyFromValue(HashMap hm, Object obj){
		Set set = hm.entrySet();
		Iterator i = set.iterator();
		
		while(i.hasNext()){
			Map.Entry me = (Map.Entry)i.next();
			if (me.getValue().equals(obj)){
				return (String)me.getKey();
			}
		}
		return "";
	}
	
	private static String getBlockInfo(String name){
		//<típus> [in: <bemenetek_száma>] [out: <kimenetek_száma>]
		Object obj = blocks.get(name);
		String blockInfo = "name: " + name + " type: ";
		if(obj instanceof AndGate){
			blockInfo += "AND in: " + ((AndGate)obj).getInputs().size();
		} else if (obj instanceof OrGate){
			blockInfo += "OR in: " + ((OrGate)obj).getInputs().size();
		} else if (obj instanceof NotGate){
			blockInfo += "NOT";
		} else if (obj instanceof LED){
			blockInfo += "LED";
		} else if (obj instanceof Switch){
			blockInfo += "SWITCH";
		} else if (obj instanceof SequenceGen){
			blockInfo += "SEQGEN";
		} else if (obj instanceof Oscilloscope){
			blockInfo += "SCOPE";
		} else if (obj instanceof Composite){
			blockInfo += "COMPOSITE in: " + ((Composite)obj).getInputs().size() + " out: " + ((Composite)obj).getOutputs().size();
		} 
		return blockInfo;
	}
	

}
